package com.gframework.boot.flyway;

import java.util.List;
import java.util.NoSuchElementException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.autoconfigure.flyway.FlywayProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;

import com.gframework.annotation.flyway.EnableFlywayExtend;
import com.gframework.util.GUtils;

/**
 * spring执行监听器，在适当的时候执行flyway
 * 
 * @since 2.0.0
 * @author Ghwolf
 */
public class FlywaySpringRunListener implements SpringApplicationRunListener{
	private static final String FLYWAY_ENABLED_PROPERTIES_KEY = "spring.flyway.enabled";
	
	private static final String SPRING_DEFAULT_DBURL_KEY = "spring.datasource.url";
	private static final String SPRING_DEFAULT_DBUSERNAME_KEY = "spring.datasource.username";
	private static final String SPRING_DEFAULT_DBPASSWORD_KEY = "spring.datasource.password";
	/**
	 * 是否开启
	 */
	private final boolean enabled ;
	
	FlywaySpringRunListener(boolean enabled){
		this.enabled = enabled;
	}
	
	// spring listener default constructor
	public FlywaySpringRunListener(SpringApplication app,String[] args){
		boolean enabled = false ;
		for (Object c : app.getAllSources()) {
			if (c instanceof Class) {
				enabled = (AnnotationUtils.findAnnotation((Class<?>)c,EnableFlywayExtend.class) != null);
				if (enabled) {
					break ;
				}
			}
		}
		this.enabled = enabled ;
	}
	
	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		if (!this.isEnabled(environment)) {
			return ;
		}
		
		try {
			doEnvironmentPrepared(environment);
		} catch(Exception e) {
			LoggerFactory.getLogger(FlywaySpringRunListener.class).error("flyway执行出错！", e);
			try {
				// 兼容异步日志输出，等待日志输出完毕
				Thread.sleep(500);
			} catch (InterruptedException e1) {
			}
			throw e ;
		}
	}
	
	private void doEnvironmentPrepared(ConfigurableEnvironment environment) {
		Binder binder = Binder.get(environment);
		FlywayProperties pro;
		try {
			pro = binder.bind("spring.flyway", FlywayProperties.class).get();
		} catch(NoSuchElementException e) {
			// 没有配置flyway相关信息，忽略执行
			return ;
		}
		
		if (StringUtils.isEmpty(pro.getUrl())) {
			pro.setUrl(environment.getProperty(SPRING_DEFAULT_DBURL_KEY));
		}
		if (StringUtils.isEmpty(pro.getUser())) {
			pro.setUser(environment.getProperty(SPRING_DEFAULT_DBUSERNAME_KEY));
		}
		if (StringUtils.isEmpty(pro.getPassword())) {
			pro.setPassword(environment.getProperty(SPRING_DEFAULT_DBPASSWORD_KEY));
		}
		
		
		List<IFlywayExtendSPI> spis = SpringFactoriesLoader.loadFactories(IFlywayExtendSPI.class,
				Thread.currentThread().getContextClassLoader());
		
		for (IFlywayExtendSPI spi : spis) {
			spi.intercept(pro, environment);
			GUtils.setIfNotEmpty(spi.getUrl(pro.getUrl(), environment), pro :: setUrl);
			GUtils.setIfNotEmpty(spi.getUsername(pro.getUser(), environment), pro :: setUser);
			GUtils.setIfNotEmpty(spi.getPassword(pro.getPassword(), environment), pro :: setPassword);
			List<String> locations = spi.getLocation(pro.getUrl(), environment);
			if (locations != null && !locations.isEmpty()) {
				pro.getLocations().addAll(locations);
			}
		}
		
		new FlywayExecute().doRunFlyway(pro, environment);
	}
	
	/**
	 * 判断是否开启扩展flyway操作，即同时满足 启用{@link EnableFlywayExtend} 并且 {@value #FLYWAY_ENABLED_PROPERTIES_KEY}=true
	 */
	private boolean isEnabled(ConfigurableEnvironment environment){
		return this.enabled && !Boolean.FALSE.equals(environment.getProperty(FLYWAY_ENABLED_PROPERTIES_KEY,Boolean.class));
	}
	

	// 兼容 springboot 2.1.* 
	@Override
	public void starting() {
	}
	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
	}
	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
	}
	@Override
	public void started(ConfigurableApplicationContext context) {
	}
	@Override
	public void running(ConfigurableApplicationContext context) {
	}
	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
	}
	
	
}
